feat(hooks): session lifecycle hooks + payload sessionId + cli send hookContext#60
Conversation
Review 汇总(Claude Code + Codex 双人 review)整体方向认可:fire-and-forget 兜底干净、默认不配 hooks 时零行为变化、向后兼容、 必须修(合并前)P1 | hook 子进程生命周期:timeout 不杀进程树,CLI 会被挂住
建议: P2 | hook 继承完整环境,泄漏机密 P2 | quote-reply 路径漏传 建议修(非阻塞 P3)
已排除每次 emit 的 合并前验收口径
@Lotu527 想确认一下你近期是否有时间处理上面的 P1 + 两个 P2(P3 可顺手但别扩大改动面)?如果你最近排不开,我们这边可以接手按上述验收口径来修、改完会再做一轮 focused re-review。你方便的话回个话 🙏 |
|
@deepcoldy OK,今天我处理下 |
重新 review 后续(5 个新 commit 之后)感谢快速响应,先放结论:上一轮 6 个 finding 我们逐条核了源码,P1 子进程生命周期、P2 env 泄漏、P2 quote 不过新一轮 review 又冒出 3 个点,1 个是已修 P1 的边角余量、2 个是这次新引入的,建议合并前都处理一下。 1. 🔴
|
|
@deepcoldy 近期事情有点忙可能来不处理,你们可以先接手处理下 |
|
@Lotu527 好的,没问题,我抽空试着改改看 |
8bef934 to
f2671fb
Compare
…最新 master) 把 PR deepcoldy#60 整段在 origin/master HEAD(78ff7ce)上重新落地,去掉两轮 merge 的 噪声,便于整体 review。原 PR 全部内容均保留。 新增能力: - src/services/hook-runner.ts:emitHookEvent / loadHookConfigs / prepareHookPayload / parseHookCommand。fire-and-forget 跑外部 hook, detached + 进程组超时清理,env 最小化 allowlist(不带 LARK_APP_SECRET 等 ambient secret),CONTENT_FIELDS 600 字符截断 + xxxLength / xxxTruncated 标记。 - 9 个 hook 事件:topic.new / thread.reply / outbound.send / outbound.reply / schedule.fired / session.start / session.exit / session.idle / session.requires_attention,预埋 session.error。 - daemon.ts / im/lark/client.ts / core/scheduler.ts / core/worker-pool.ts / cli.ts 各接入点。 - src/cli/send-dispatch.ts 抽出,普通群引用回复 / withdrawn fallback 均带 hookContext。 - examples/hooks/ 三个示例脚本(echo-to-log / osascript-notify / http-webhook)。 - 文档:docs-site/docs/hooks.md(Rspress 多页站,「功能详解」组紧邻 定时任务)。 - 测试:hook-runner / lifecycle / dispatch / installer / cache 共 6 文件 31 用例。 冲突解决(vs 78ff7ce): - src/cli.ts:采用 master 的 sendTarget 抽象(chatId / rootMessageId 字段名 + recordBridgeSendMarker 新签名含 sentContent),PR 透传的 hookContext 挂到 sendMessage / replyMessage 尾参。 - src/daemon.ts:采用 master 新增的 syncReplyTargetState / resolveSessionReplyTarget reply-target 跟踪;PR 的 hookContext 透传到 target===thread 早返、topic-mode 回退、最终 sendMessage / replyMessage 四处全部带上。 - src/types.ts:master 给 user_notify 加 turnId,本 PR 加 pty_error,二者 合并不冲突。 - docs-site/rspress.config.ts auto-merge OK,Lifecycle Hooks 与 master 新加的「接入点(Webhook)」共存。 已知 review 残留(前两轮共识,未在本 squash 修复,等作者跟进): - 🔴 session.error 死代码:原 dbcd0b3 commit message 写 worker.ts 加 PTY 扫描器但 worker.ts 没进 commit,没人 send pty_error IPC。 - 🟠 嵌套 redaction 字段打错:prepareHookPayload 截 payload['options'], worker-pool 实际 emit 字段是 optionsPreview,等于没截;测试用合成 options 字段所以是绿的。 - 🟡 CLI 路径 timeout 不杀进程组:fireAndForget unref 了 timer,botmux send 退出快但 hook 长跑会变孤儿。修法是 daemon-supervised forwarding。 验证:tsc --noEmit 通过;6 个 hook 相关测试文件 31 用例全过; docs-site pnpm build 通过,hooks 页面正常渲染。 Co-Authored-By: gaozhikun <gaozhikun@bytedance.com> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… 整合版) 把 PR deepcoldy#60 整段在 origin/master HEAD(78ff7ce)上重新落地,并修掉前两轮 review 共识的三项残留。 ## 新增能力 - src/services/hook-runner.ts:emitHookEvent / loadHookConfigs / prepareHookPayload / parseHookCommand。fire-and-forget 跑外部 hook, detached + 进程组超时清理,env 最小化 allowlist(不带 LARK_APP_SECRET 等 ambient secret),CONTENT_FIELDS 600 字符截断 + xxxLength / xxxTruncated 标记。 - 9 个 hook 事件:topic.new / thread.reply / outbound.send / outbound.reply / schedule.fired / session.start / session.exit / session.idle / session.requires_attention。 - daemon.ts / im/lark/client.ts / core/scheduler.ts / core/worker-pool.ts / cli.ts 各接入点。 - src/cli/send-dispatch.ts 抽出:普通群引用回复 / withdrawn fallback 均带 hookContext。 - examples/hooks/ 三个示例脚本(echo-to-log / osascript-notify / http-webhook)。 - 文档:docs-site/docs/hooks.md(Rspress 多页站,「功能详解」组紧邻 定时任务)。 - 测试:hook-runner / lifecycle / dispatch / installer / cache 共 6 文件 33 用例。 ## 本轮 reviewer 修掉的三项残留 ### 🔴 session.error 死代码 → revert 原 dbcd0b3 的 PTY 错误扫描器只接了消费侧(worker-pool case + emit helper + HOOK_EVENTS + pty_error IPC 类型),但生产侧(worker.ts 扫描器)漏 git add。 本提交 revert 整套 session.error 脚手架——HOOK_EVENTS 去掉 session.error, SessionLifecycleEvent 去掉 'session.error',删除 emitSessionErrorHook, worker-pool 的 case 'pty_error' 块整段删,types.ts 去掉 pty_error IPC 类型, worker-pool import 同步收紧。后续作者若要补 PTY 扫描器,按自身设计拍板单独 开 PR 更合理(哪些错误正则、去重窗口、误报评估等)。 ### 🟠 嵌套 redaction 字段名打错 → 改对 + 加测 prepareHookPayload 原本截 payload['options'],但 worker-pool tui_prompt 真实 emit 的字段是 optionsPreview,等于绕过 600 字符截断。改成同时识别 ['optionsPreview', 'options'] 两个字段(loop 处理),向后兼容 + 覆盖真实 emit。补一条断言走 optionsPreview 真实路径的测试,避免合成字段绿测的盲区。 ### 🟡 CLI 路径 timeout 不杀进程组 → daemon 监工 原 fireAndForget 把 timer.unref 了,botmux send 退出快但短命 CLI 进程的 timer 没机会触发,长跑 hook 会变永久后台孤儿。改用 daemon-supervised forwarding: - emitHookEvent 检测 BOTMUX_SESSION_ID + BOTMUX_LARK_APP_ID 双 env(仅 CLI spawn 出来的进程有),命中则 POST /api/hooks/emit 到本机 daemon(findOnline Daemon 发现端口)立刻返回;本进程不 spawn 任何 hook,botmux send 仍毫秒级退出。 - daemon.ts 新增 ipcRoute('POST', '/api/hooks/emit', ...) 校验 event 在 HOOK_EVENTS 里 + payload 是 object,然后 emitHookEvent 本机派发。 - daemon 进程本身没有 BOTMUX_SESSION_ID,所以不会递归转发;event loop 长寿命, timer 不被 unref 即可触发,超时 process.kill(-pid) 进程组清理重新生效。 - daemon 不可达:findOnlineDaemon 返回 null → warn 日志后 drop,符合 hooks best-effort 语义。 - 测试:新增 'CLI context forwards to daemon instead of spawning locally', spawnSync CLI 子进程设双 env + 配置本地 spawn 标记 hook,断言无 daemon 时 marker 文件不存在(=本地未 spawn)。 ## 冲突解决(vs 78ff7ce) - src/cli.ts:用 master 的 sendTarget 抽象(sendTarget.chatId / sendTarget.rootMessageId 字段名 + recordBridgeSendMarker 新签名带 sentContent),hookContext 透传到 sendMessage / replyMessage 尾参。 - src/daemon.ts:用 master 新增的 syncReplyTargetState / resolveSessionReplyTarget reply-target 跟踪;hookContext 透传到 target===thread 早返、topic-mode 回退、最终 sendMessage / replyMessage 四处全覆盖。 - src/types.ts:master 给 user_notify 加 turnId,本 PR 加 pty_error 后又被 revert,留 user_notify.turnId。 - docs-site/rspress.config.ts auto-merge 干净,Lifecycle Hooks 与 master 新增 「接入点(Webhook)」共存。 ## 验证 - tsc --noEmit 通过; - 6 个 hook 相关测试文件 33 用例全部通过(含新增的 optionsPreview 真实路径 + CLI 转发行为两条); - docs-site pnpm build 通过,hooks 页面正常渲染。 Co-Authored-By: gaozhikun <gaozhikun@bytedance.com> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
f2671fb to
1334ed1
Compare
|
Merged 🎉 感谢 @Lotu527 这一轮的快速迭代,前两轮 review 提的 6 个 finding 都被你扎实处理掉了。最后一轮我们以 reviewer 身份把 3 个残留也整合进来:
完整 dual review 走完:Codex 独立 PASS(含 daemon-supervised harness 端到端实测:CLI 205ms 退出 / timeout 触发 / 800ms 后 survivor=0);tsc clean;33 hook tests 全过。 merge commit: da90722,未发版(待 deepcoldy 决定)。后续 PTY scanner 那块期待你开新 PR,我们继续配合 review 🙏 |
Summary
sessionIdin lifecycle/outbound payloads so external integrations can correlate events with botmux sessions.session.start,session.exit,session.idle, andsession.requires_attention.examples/hooks/scripts.Commits
3170f03feat(hooks): add lifecycle hook payloads with session ids0102ef2fix(hooks): include session id for cli sends66a648cfeat(hooks): add session lifecycle hook eventse21bfc5docs(hooks): align session.* payload schema with implementatione88403ftest(hooks): update lifecycle start mock after rebaseca4f56fdocs(hooks): document generic hook usage independent of AmazingIslandTest plan
pnpm exec tsc --noEmitpnpm buildbash -n examples/hooks/*.sh,echo-to-log.shlocal payload appendAcknowledgement
Hooks are intentionally generic: commands are arbitrary executables, receive JSON on stdin, and get
BOTMUX_HOOK_EVENTin the environment. Failures/timeouts are logged and do not block botmux flows.